#include <gtest/gtest.h>
#include <iostream>
#include <random>
#include <vector>
#include <future>

#include "ThreadPool/ThreadPool.h"

std::random_device rd;
std::mt19937 mt(rd());
std::uniform_int_distribution<int> dist(0, 10);
auto rnd = std::bind(dist, mt);

void simulate_hard_computation() {
    std::this_thread::sleep_for(std::chrono::milliseconds(10 + rnd()));
}

void multiply(const int a, const int b) {
    simulate_hard_computation();
    const int res = a * b;
}

void multiply_output(int& out, const int a, const int b) {
    simulate_hard_computation();
    out = a * b;
}

int multiply_return(const int a, const int b) {
    simulate_hard_computation();
    return a * b;
}

class ThreadPoolTest : public ::testing::Test {
protected:
    void SetUp() override {
        pool = std::make_unique<ThreadPool>(3);
        pool->init();
    }
    
    void TearDown() override {
        pool->shutdown();
    }
    
    std::unique_ptr<ThreadPool> pool;
};

TEST_F(ThreadPoolTest, SubmitsAndExecutesTasks) {
    const int taskCount = 20;
    std::vector<std::future<void>> futures;
    
    for (int i = 0; i < taskCount; ++i) {
        futures.emplace_back(
            pool->submit(multiply, i, i+1)
        );
    }
    
    for (auto& fut : futures) {
        ASSERT_NO_THROW(fut.get());
    }
}

TEST_F(ThreadPoolTest, HandlesOutputParameters) {
    int result = 0;
    auto future = pool->submit(multiply_output, std::ref(result), 7, 6);
    
    future.get();
    ASSERT_EQ(result, 42); // 7 * 6=42
}

TEST_F(ThreadPoolTest, ReturnsCorrectValues) {
    auto future = pool->submit(multiply_return, 8, 9);
    
    ASSERT_EQ(future.get(), 72); // 8 * 9=72
}

TEST_F(ThreadPoolTest, HandlesConcurrentAccess) {
    const int taskCount = 50;
    std::vector<std::future<int>> futures;
    
    for (int i = 1; i <= taskCount; ++i) {
        futures.emplace_back(
            pool->submit(multiply_return, i, i)
        );
    }
    
    for (int i = 0; i < taskCount; ++i) {
        int expected = (i+1) * (i+1);
        ASSERT_EQ(futures[i].get(), expected);
    }
}

int main(int argc, char** argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}